﻿using FySystem.Net.FyNetConnection.Src;
using FySystem.Net.FyNetConnection.Src.Packing;
using FySystem.Net.FyNetConnection.Src.SubPacking;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using static FySystem.Net.FyNetConnection.FyServer;

namespace FySystem.Net.FyNetConnection
{
    public class AsyncFyClient : IDisposable
    {
        private Socket _client;
        private IPEndPoint _remoteEP;
        /// <summary>
        /// 远程连接
        /// </summary>
        public Socket Client { get { return _client; } }

        /// <summary>
        /// 远端信息
        /// </summary>
        public IPEndPoint RemoteEP { get { return _remoteEP; } }

        /// <summary>
        /// 分包模式，默认未InnerMode，如果EndFlag未设置，则不分包
        /// </summary>
        public SubPackageMode SubPackageMode { get; set; } = SubPackageMode.InnerMode;

        /// <summary>
        /// 消息末尾结束标记，如果不设置，则收到一条是一条，不对粘包进行处理
        /// </summary>
        public byte[] EndFlag { get; set; } = null;

        /// <summary>
        /// 如果收到了数据要自己处理分包，则在该委托中处理
        /// </summary>
        public OnPacking PackingFun { get; set; }

        /// <summary>
        /// 与服务器连接成功回调
        /// </summary>
        public OnAcceptHandler ConnectCallback { get; set; }

        /// <summary>
        /// 数据接收回调
        /// </summary>
        public Action<StateObjectBase, byte[]> ReceiveCallback { get; set; }

        /// <summary>
        /// 出错回调
        /// </summary>
        public Action<Exception, Socket> ExceptionCallback { get; set; }

        /// <summary>
        /// 如果出错了，这个就会返回false，暂时无法判断连接是否断开，这个返回false就认为是断开了，需要释放当前连接，并重连
        /// </summary>
        public bool IsConnected { get; set; } = true;

        /// <summary>
        /// 超时时间
        /// </summary>
        public int ReceiveTimeout
        {
            get { return Client.ReceiveTimeout; }
            set { Client.ReceiveTimeout = value; }
        }

        /// <summary>
        /// 创建连接客户端
        /// </summary>
        /// <param name="ipAddress"></param>
        /// <param name="port"></param>
        public AsyncFyClient(IPAddress ipAddress, int port)
        {
            try
            {
                _remoteEP = new IPEndPoint(ipAddress, port);
                _client = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                _client.ReceiveTimeout = 30;
            }
            catch (Exception ex)
            {
                IsConnected = false;
                ExceptionCallback?.Invoke(ex, _client);
            }
        }

        /// <summary>
        /// 连接
        /// </summary>
        public void Connect(bool needReceive)
        {
            try
            {
                _client.BeginConnect(_remoteEP, MyConnectCallback, null);

                //监听是否有收到数据
                if (needReceive)
                {
                    StateObjectBase stateObject = null;
                    if (this.ConnectCallback == null)
                        stateObject = new StateObjectBase();
                    else
                        stateObject = ConnectCallback(null, _client);

                    stateObject.WorkSocket = _client;

                    _client.BeginReceive(stateObject.Buffer, 0, stateObject.BufferSize, SocketFlags.None, MyReadCallback, stateObject);
                }
            }
            catch (Exception ex)
            {
                IsConnected = false;
                ExceptionCallback?.Invoke(ex, _client);
            }
        }

        /// <summary>
        /// 释放之前的连接，然后重连
        /// </summary>
        /// <param name="needReceive"></param>
        public void Reconnect(bool needReceive)
        {
            try
            {
                if (_client != null)
                {
                    try
                    {
                        _client.Dispose();
                    }
                    catch { }
                }

                _client = new Socket(_remoteEP.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                _client.ReceiveTimeout = 30;

                Connect(needReceive);
            }
            catch (Exception ex)
            {
                IsConnected = false;
                ExceptionCallback?.Invoke(ex, _client);
            }
        }

        /// <summary>
        /// 连接成功回调
        /// </summary>
        /// <param name="ar"></param>
        private void MyConnectCallback(IAsyncResult ar)
        {
            try
            {
                _client.EndConnect(ar);
                IsConnected = true;
                this.ConnectCallback?.Invoke(null, _client);
            }
            catch (Exception ex)
            {
                IsConnected = false;
                ExceptionCallback?.Invoke(ex, _client);
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="packingMode"></param>
        public void Send(byte[] bytes, PackingMode packingMode)
        {
            try
            {
                bytes = PackingData(bytes, packingMode);
                _client.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, MySendCallback, null);
            }
            catch (Exception ex)
            {
                IsConnected = false;
                ExceptionCallback?.Invoke(ex, _client);
            }
        }

        /// <summary>
        /// 发送回调
        /// </summary>
        /// <param name="ar"></param>
        private void MySendCallback(IAsyncResult ar)
        {

        }

        private byte[] PackingData(byte[] bytes, PackingMode packingMode)
        {
            byte[] res = null;
            try
            {
                switch (packingMode)
                {
                    case PackingMode.None:
                        res = bytes;
                        break;
                    case PackingMode.FyModeV1:
                        res = PackingWithFyModeV1.PackData(bytes);
                        break;
                }
            }
            catch (Exception)
            {
                throw;
            }
            return res;
        }

        /// <summary>
        /// 数据接收回调
        /// </summary>
        /// <param name="ar"></param>
        private void MyReadCallback(IAsyncResult ar)
        {
            Socket handler = null;
            try
            {
                StateObjectBase stateObject = (StateObjectBase)ar.AsyncState;
                stateObject.LastReceivedTime = DateTime.Now;
                handler = stateObject.WorkSocket;
                int bytesRead = handler.EndReceive(ar);

                if (bytesRead > 0)
                {
                    List<byte[]> segments = null;
                    switch (this.SubPackageMode)
                    {
                        case SubPackageMode.InnerMode:
                            //按照关键字节分割
                            segments = SubPackingWithInnerMode.SubPacking(this.EndFlag, stateObject);
                            break;
                        case SubPackageMode.FyModeV1:
                            //按照第一个版本的内部规则分割
                            segments = SubPackingWithFyModeV1.SubPacking(bytesRead, stateObject);
                            break;
                        case SubPackageMode.CustomMode:
                            //自定义分割
                            segments = PackingFun(bytesRead, stateObject);
                            break;
                    }

                    handler.BeginReceive(stateObject.Buffer, 0, stateObject.BufferSize, 0, MyReadCallback, stateObject);

                    //以下代码可能比较耗时，放最后
                    if (ReceiveCallback != null && segments != null)
                    {
                        for (int i = 0; i < segments.Count; i++)
                        {
                            ReceiveCallback.Invoke(stateObject, segments[i]);
                        }
                    }
                }
                else
                {
                    //目前来说收到0字节不能确定就是关闭，所以代码保留
                }
            }
            catch (Exception ex)
            {
                IsConnected = false;
                ExceptionCallback?.Invoke(ex, handler);
            }
        }

        public void Dispose()
        {
            try
            {
                if (_client != null)
                {
                    _client.Dispose();
                }
            }
            catch
            { }
        }
    }
}
